#include "mxmcallbacks.h"
#include <vector>
#include <iterator>
#include <stdexcept>
#include <iostream>
#include <time.h>
#include <string.h>
using std::cout;
using std::endl;
using std::cerr;
using std::logic_error;

using namespace v8;
using std::string;

bool isExternal = false;
char mxmexpr[300];

Persistent<ObjectTemplate> ME_template;
Local<Value> MaximaError(Handle<String> input, Handle<String> output)
{	
    HandleScope handle_scope;
    Local<ObjectTemplate> MEtemplate = ObjectTemplate::New();
	MEtemplate->Set(String::New("maxima_error"), Integer::New(1));
	MEtemplate->Set(String::New("input"), input);
	MEtemplate->Set(String::New("output"), output);
	//result->SetNamedPropertyHandler(MSgetter, MSsetter);
    Local<Object> obj;
    obj = Local<Object>::New(MEtemplate->NewInstance());
    return obj;
}

string char2hex( char dec )
{
    char dig1 = (dec&0xF0)>>4;
    char dig2 = (dec&0x0F);
    if ( 0 <= dig1 && dig1 <= 9) dig1 += 48;    //0,48inascii
    if (10 <= dig1 && dig1 <=15) dig1 += 97 - 10; //a,97inascii
    if ( 0 <= dig2 && dig2 <= 9) dig2 += 48;
    if (10 <= dig2 && dig2 <=15) dig2 += 97 - 10;

    string r;
    r.append( &dig1, 1);
    r.append( &dig2, 1);
    return r;
}

string urlencode(string const & c)
{   
    string escaped = "";
    int max = c.length();
    for(int i = 0; i < max; i++)
    {
        if ( isalnum(c[i]) || (c[i] == '~') || (c[i] == '!') || (c[i] == '*') || (c[i] == '(') || (c[i] == ')') || (c[i] == '\'') )
        {
            escaped.push_back(c[i]);
        }
        else
        {
            escaped.append("%");
            escaped.append( char2hex(c[i]) );	//converts char 255 to string "ff"
        }
    }
    return escaped;
}

Handle<Value> MevalCallback(const Arguments& args)		// Callback for meval (send an expression to Maxima and get the evaluated result as a string).
{
	if (args.Length() < 1) return v8::Undefined();
	HandleScope scope;
	Handle<Value> arg1 = args[0];
	String::AsciiValue tmpexpr(arg1);
	if (!isExternal)
	{
		strcpy_s(mxmexpr, 300, *tmpexpr);
	}
	isExternal = false;
	char str[300];

	if (strcmp(mxmConnect.c_str(), "file") == 0)
	{
		sprintf_s(str, "string(%s);", mxmexpr);
		FILE* fmaxout;
		fopen_s(&fmaxout, "maxima.txt", "wt");
		fprintf(fmaxout, "%s", str);
		fclose(fmaxout);
		char str[100];
		FILE* fmaxin = _popen(maximaPipeCmd, "rt");
		if (fmaxin == NULL) 
		{
			char str[100];
			sprintf_s(str, "Couldn't open maxima, check whether your path is correct: %s.\n", maximaPipeCmd);
			throw logic_error(str);
		}
		char * pch;
		char * nexttoken;
		string messagestr;
		bool inputline = false;
		while (fgets(str, sizeof(str), fmaxin))
		{
			if ((strstr(str, "%o2") != NULL) && (strstr(str, "maxima.txt") == NULL))
			{
				strtok_s(str, " ", &nexttoken);
				pch = strtok_s(NULL, " ", &nexttoken);
				if (*pch == '-')
				{
					pch = strtok_s(NULL, " ", &nexttoken);
				}
				_pclose(fmaxin);
				return String::New(pch);
			}
			else if (inputline) 
			{
				messagestr.append(str);
			}
			if (strstr(str, "batching") != NULL)
			{
				fgets(str, sizeof(str), fmaxin);
				if (strstr(str, "%i2") == NULL)
				{
					string strtmp = str;
					messagestr.append(strtmp);
					inputline = true;
				}
			}
		}
		_pclose(fmaxin);
		// If we didn't exit the function by now, something's wrong.

		char msgstr[600];
		sprintf_s(msgstr, 600, "%s", messagestr.c_str());
		return ThrowException(MaximaError(String::New(mxmexpr), String::New(msgstr)));
	}
	else if (strcmp(mxmConnect.c_str(), "local") == 0)
	{
		sprintf_s(str, "string(%s);", mxmexpr);
		send(Socket, str, strlen(str), 0);
		char buffer[401];
		char tmpbuffer[401];
		int iResult;
		char * pch;
		char * nexttoken;
		bool start = false;
		string messagestr;
        do {
			iResult = recv(Socket, buffer, 400, 0);
			if (iResult > 0)
			{
				buffer[iResult] = '\0';
				if ((pch = strstr(buffer, "(%i")) != NULL)
				{	
					messagestr.append(pch);
					start = true;
				}
				if ( strstr(buffer, "%o") != NULL )
				{
					pch = strstr(buffer, "%o");
					strcpy_s(tmpbuffer, pch);
					pch = strtok_s(tmpbuffer, " ", &nexttoken);
					pch = strtok_s(NULL, " ", &nexttoken);
					if (*pch == '-')
					{
						pch = strtok_s(NULL, " ", &nexttoken);
					}
					break;
				} 
				if ((strstr(buffer, "error") != NULL) || (strstr(buffer, "Incorrect") != NULL))
				{
					while ((strstr(buffer, "^") == NULL) && (iResult > 0))
					{
						iResult = recv(Socket, buffer, 400, 0);
						if (iResult > 0)
						{
							buffer[iResult] = '\0';
						}
						if (((pch = strstr(buffer, "(%i")) != NULL) && !start)
						{	
							messagestr.append(pch);
							start = true;
						}
						messagestr.append(buffer);
					}
					char msgstr[600];
					sprintf_s(msgstr, 600, "%s", messagestr.c_str());
					return ThrowException(MaximaError(String::New(mxmexpr), String::New(msgstr)));
					//cerr << "Error in Maxima. Your input: " << mxmexpr << "\nMaxima answer:\n" << endl;
					//throw std::logic_error(buffer);
				}
			}
			else if (iResult == 0)
			{
				cout << "Connection closed\n" << endl;
			}
			else if (iResult < 0)
			{
				char str[100];
				sprintf_s(str, "Function recv failed with error: %d\n", WSAGetLastError());
				throw std::runtime_error(str);
			}

		} while (iResult > 0);
		return String::New(pch);
	}
	else {
		string strexprtmp(mxmexpr);
		string strexpr = urlencode(strexprtmp);
		sprintf_s(str, "GET /~mobichel/maxima.jsp?expr=%s&format=string HTTP/1.1\r\nHost: %s\r\n\r\n", strexpr.c_str(), servername.c_str());
		int errnum = 0;
		time_t starttime = time(NULL);
		char buffer[600];
		int iResult;
		do {
			if (time(NULL) - starttime > 2)
			{
				throw std::runtime_error("Couldn't establish connection to server.");
			}
			send(Socket, str, strlen(str), 0);
			iResult = recv(Socket, buffer, 600, 0);
			if (iResult > -1) 
			{
				buffer[iResult] = '\0';
			}
			errnum = WSAGetLastError();
			if (errnum == 10053) 
			{
				connect(Socket, (SOCKADDR*)(&SockAddr), sizeof(SockAddr));
			} else if (errnum != 0)
			{
				cout << "Error in recv: " << errnum << endl;
				throw std::runtime_error("Failed to receive an answer from the server.");
			}
		} while ((errnum == 10053) || (iResult == 0));
		if (strstr(buffer, "200") == NULL)
		{
			cout << buffer << endl;
			throw std::runtime_error("Maxima didn't receive an answer from the server.");
		} 
		char * pch = strstr(buffer, "\r\n\r\n") + 4;
		if ((strstr(buffer, "error") != NULL) || (strstr(buffer, "Incorrect") != NULL))
		{
			return ThrowException(MaximaError(String::New(mxmexpr), String::New(pch)));
		}
		return String::New(pch);
	}
}

Handle<Value> MevalIntCallback(const Arguments& args)	// Callback for mevalInt (send an expression to Maxima and get an integer result).
{
	String::AsciiValue str(MevalCallback(args));	
	char * strres = *str;
	int i = 0;
	if (*strres == '-')
	{
		++i;
	}
	if (!isdigit(*(strres+i)))
	{
		throw logic_error("MevalInt error: the result is not a number.\n");
	}
	int result = atoi(strres);
	double dres = atof(strres);
	if (dres != (double)result)
	{
		throw logic_error("MevalInt error: the result is not an integer.\n");
	}
	return Integer::New(result);
}

Handle<Value> MevalNumCallback(const Arguments& args)	// Callback for mevalNum (send an expression to Maxima and evaluate it numerically).
{
	if (args.Length() < 1) return v8::Undefined();
	HandleScope scope;
	Handle<Value> arg1 = args[0];
	String::AsciiValue expr(arg1);
	char strexpr[300];
	sprintf_s(strexpr, "ev(%s, numer)", *expr);
	strcpy_s(mxmexpr, 300, strexpr);
	isExternal = true;
	String::AsciiValue str(MevalCallback(args));	
	isExternal = false;
	char * strres = *str;
	int i = 0;
	if (*strres == '-')
	{
		++i;
	}
	if (!isdigit(*(strres+i)))
	{
		throw logic_error("MevalNum error: the result is not a number.\n");
	}
	double result = atof(strres);
	return Number::New(result);
}

Handle<Value> IsEqualCallback(const Arguments& args)	// Callback for isEqual (returns true if the two input expressions are equal).
{
	if (args.Length() != 2) 
	{
		throw logic_error("Function isEqual takes exactly 2 arguments.");
	}
	HandleScope scope;
	Handle<Value> arg1 = args[0];
	String::AsciiValue expr1(arg1);
	Handle<Value> arg2 = args[1];
	String::AsciiValue expr2(arg2);
	char strexpr[300];
	sprintf_s(strexpr, "ev((%s) - (%s), numer)", *expr1, *expr2);
	strcpy_s(mxmexpr, 300, strexpr);
	isExternal = true;
	String::AsciiValue str(MevalCallback(args));	
	isExternal = false;
	char * strres = *str;
	int i = 0;
	if (*strres == '-')
	{
		++i;
	}
	if (!isdigit(*(strres+i)))
	{
		throw logic_error("IsEqual error: couldn't compare the expressions.\n");
	}
	double result = atof(strres);
	if (fabs(result) < 0.000001)
	{
		return Boolean::New(1);
	} else {
		return Boolean::New(0);
	}	
}

Handle<Value> TexCallback(const Arguments& args)	// Callback for tex (get the specified expression in TeX-format).
{
	if (args.Length() < 1) return v8::Undefined();
	HandleScope scope;
	Handle<Value> arg1 = args[0];
	String::AsciiValue expr(arg1);
	char strexpr[300];
	sprintf_s(strexpr, "tex(%s)", *expr);
	strcpy_s(mxmexpr, 300, strexpr);
	isExternal = true;
	String::AsciiValue str(MevalCallback(args));	
	isExternal = false;

	return String::New(*str);
}
